﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ReactiveGraph.Core;

namespace ReactiveGraph.Object
{
    public abstract class ReactiveObject<T> : 
        IReactiveObject
        where T : ReactiveObject<T>
    {
        private Context _context;
        private ReactiveNode _internalNode;

        public IObservable<IProperty> PropertyChanged()
        {
            return Events
                .Where(e => e.Type == EventType.PropertyChange && this == e.Source.Node)
                .Select(e => e.Source.Property);
        }

        public IObservable<IProperty> PropertyChanged(Func<T,IProperty> selectProperty)
        {
            return PropertyChanged().Where(property => selectProperty(this as T) == property);
        }

        public IObservable<IProperty> DescendantPropertyChanged()
        {
            return Events
                .Where(e => e.Type == EventType.PropertyChange)
                .Select(e => e.Source.Property);
        }

        public void SetContext(Context context)
        {
            _context = context;
            _internalNode = Context.Factories
                .CreateNamedNodeForType(Context.Factories.GetNameForNode(this), typeof(ReactiveNode)) as ReactiveNode;
            _internalNode.SetConceptualIdentity(this);
        }

        public virtual void Initialize()
        {
            InstantiateProperties();
            InstantiateChildNodes();
        }

        protected Context Context
        {
            get { return _context; }
        }

        private void InstantiateChildNodes()
        {
            var nodes = GetType().GetProperties()
                .Where(info => !info.GetIndexParameters().Any())
                .Where(info => typeof(IReactiveNode).IsAssignableFrom(info.PropertyType));
            nodes.Run(info =>
            {
                var newNode = Context.Factories.CreateNamedNodeForType(info.Name,info.PropertyType);
                _internalNode.ChildNodes.Add(newNode);
                info.SetValue(this, newNode, null);
            });
        }

        private void InstantiateProperties()
        {
            var properties = GetType().GetProperties()
                .Where(IsSupportedAutoInstantiateProperty);
            properties.Run(info =>
                               {
                                   
                                   var property = Context.Factories.CreateProperty(info.Name, info.PropertyType);
                                   if(property == null) return;
                                   _internalNode.AttachProperty(property);
                                   info.SetValue(this, property, null);
                               });
        }

        

        private static bool IsSupportedAutoInstantiateProperty(PropertyInfo info)
        {
            return  typeof(IProperty).IsAssignableFrom(info.PropertyType);
        }


        public IObservable<Unit> Behaviours
        {
            get { return _internalNode.Behaviours; }
        }

        protected void AttachCustomProperty(IProperty property)
        {
            _internalNode.AttachProperty(property);
        }

        public void AttachBehaviour(IObservable<Unit> behaviour)
        {
            _internalNode.AttachBehaviour(behaviour);
        }

        public void DetachBehaviour(IObservable<Unit> behaviour)
        {
            _internalNode.DetachBehaviour(behaviour);
        }

        IObservable<NodeEvent> IReactiveNode.Events
        {
            get { return Events; }
        }

        protected IObservable<NodeEvent> Events
        {
            get
            {
                return _internalNode.Events;
            }
        }

        public string NodeName
        {
            get { return _internalNode.NodeName; }
        }

        ReactiveNodeList IReactiveNode.ChildNodes
        {
            get { return _internalNode.ChildNodes; }
        }

        void IReactiveNode.AttachProperty(IProperty property)
        {
            _internalNode.AttachProperty(property);
        }

      
        IEnumerable<IProperty> IReactiveNode.Properties
        {
            get { return _internalNode.Properties; }
        }

        IProperty IReactiveNode.GetProperty(string name)
        {
            return _internalNode.GetProperty(name);
        }

     

        IEnumerable<IProperty> IReactiveNode.GetDescendantProperties()
        {
            return _internalNode.GetDescendantProperties();
        }

        bool IReactiveNode.HasProperty(string name)
        {
            return _internalNode.HasProperty(name);
        }


    }
}
